package com.jfireframework.codejson.function;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jfireframework.baseutil.collection.StringCache;
import com.jfireframework.baseutil.reflect.ReflectUtil;
import com.jfireframework.baseutil.smc.SmcHelper;
import com.jfireframework.baseutil.smc.compiler.JavaStringCompiler;
import com.jfireframework.baseutil.smc.model.CompilerModel;
import com.jfireframework.baseutil.smc.model.FieldModel;
import com.jfireframework.baseutil.smc.model.MethodModel;
import com.jfireframework.codejson.annotation.JsonIgnore;
import com.jfireframework.codejson.function.impl.write.IteratorWriter;
import com.jfireframework.codejson.function.impl.write.MapWriter;
import com.jfireframework.codejson.function.impl.write.StrategyMapWriter;
import com.jfireframework.codejson.function.impl.write.WriterAdapter;
import com.jfireframework.codejson.function.impl.write.array.BooleanArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.ByteArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.CharArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.DoubleArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.FloatArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.IntArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.LongArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.ShortArrayWriter;
import com.jfireframework.codejson.function.impl.write.array.StringArrayWriter;
import com.jfireframework.codejson.function.impl.write.extra.ArrayListWriter;
import com.jfireframework.codejson.function.impl.write.extra.BigDecimalWriter;
import com.jfireframework.codejson.function.impl.write.extra.DateWriter;
import com.jfireframework.codejson.function.impl.write.extra.FileWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.BooleanWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.ByteWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.CharacterWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.DoubleWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.FloatWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.IntegerWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.LongWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.ShortWriter;
import com.jfireframework.codejson.function.impl.write.wrapper.StringWriter;
import com.jfireframework.codejson.methodinfo.MethodInfoBuilder;
import com.jfireframework.codejson.methodinfo.WriteMethodInfo;
import com.jfireframework.codejson.tracker.Tracker;
import com.jfireframework.codejson.util.MethodComparator;
import com.jfireframework.codejson.util.NameTool;

public class WriterContext
{
	private static ConcurrentHashMap<Class<?>, JsonWriter>	writerMap	= new ConcurrentHashMap<Class<?>, JsonWriter>();
	private static final Logger								logger		= LoggerFactory.getLogger(WriterContext.class);
	private static AtomicInteger							count		= new AtomicInteger(1);
	
	static
	{
		writerMap.put(String.class, new StringWriter());
		writerMap.put(Double.class, new DoubleWriter());
		writerMap.put(Float.class, new FloatWriter());
		writerMap.put(Integer.class, new IntegerWriter());
		writerMap.put(Long.class, new LongWriter());
		writerMap.put(Short.class, new ShortWriter());
		writerMap.put(Boolean.class, new BooleanWriter());
		writerMap.put(Byte.class, new ByteWriter());
		writerMap.put(Character.class, new CharacterWriter());
		writerMap.put(int[].class, new IntArrayWriter());
		writerMap.put(boolean[].class, new BooleanArrayWriter());
		writerMap.put(long[].class, new LongArrayWriter());
		writerMap.put(short[].class, new ShortArrayWriter());
		writerMap.put(byte[].class, new ByteArrayWriter());
		writerMap.put(float[].class, new FloatArrayWriter());
		writerMap.put(double[].class, new DoubleArrayWriter());
		writerMap.put(char[].class, new CharArrayWriter());
		writerMap.put(String[].class, new StringArrayWriter());
		writerMap.put(ArrayList.class, new ArrayListWriter());
		writerMap.put(Date.class, new DateWriter());
		writerMap.put(File.class, new FileWriter());
		writerMap.put(java.sql.Date.class, new DateWriter());
		writerMap.put(BigDecimal.class, new BigDecimalWriter());
	}
	
	public static void write(Object entity, StringCache cache)
	{
		if (entity == null)
		{
			cache.append("null");
		}
		else
		{
			JsonWriter writer = getWriter(entity.getClass());
			try
			{
				writer.write(entity, cache, null, null);
			}
			catch (Throwable e)
			{
				e.printStackTrace();
			}
		}
	}
	
	public static JsonWriter getWriter(Class<?> ckass)
	{
		JsonWriter writer = writerMap.get(ckass);
		if (writer == null)
		{
			try
			{
				writer = (JsonWriter) createWriter(ckass, null).newInstance();
			}
			catch (Exception e)
			{
				throw new RuntimeException(e);
			}
			writerMap.putIfAbsent(ckass, writer);
		}
		return writer;
	}
	
	protected static JsonWriter getWriter(Class<?> ckass, WriteStrategy strategy)
	{
		// 这个方法每次都必须重新生成新的输出类。实际上这个方法并不是给用户直接调用的。而是给策略模式进行writer生成的。
		// 如果在这里使用writerMap进行判断，就会导致一种情况：前面已经进行过该类的输出生成了，现在有个新的策略，策略就无法生效。因为不会生成新的输出类
		Class<?> result = createWriter(ckass, strategy);
		try
		{
			Constructor<?> constructor = result.getConstructor(WriteStrategy.class);
			return (JsonWriter) constructor.newInstance(strategy);
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 创建一个输出类cklas的jsonwriter
	 * 
	 * @param ckass
	 * @return
	 */
	private static Class<?> createWriter(Class<?> ckass, WriteStrategy strategy)
	{
		if (ckass.isArray())
		{
			return implWriter(implArrayWriterMethod(ckass, strategy), ckass, strategy);
		}
		else if (Iterable.class.isAssignableFrom(ckass))
		{
			return IteratorWriter.class;
		}
		else if (Map.class.isAssignableFrom(ckass))
		{
			if (strategy == null)
			{
				return MapWriter.class;
			}
			else
			{
				return StrategyMapWriter.class;
			}
		}
		else
		{
			return implWriter(implWriteMethod(ckass, strategy), ckass, strategy);
		}
	}
	
	private static String implWriteMethod(Class<?> ckass, WriteStrategy strategy)
	{
		StringCache cache = new StringCache();
		cache.append("{\r\nStringCache cache = (StringCache)$1;\r\n");
		cache.append("Tracker _$tracker = (Tracker)$3;\r\n");
		String entityName = "entity" + count.incrementAndGet();
		cache.append(SmcHelper.getTypeName(ckass) + " " + entityName + " =(" + SmcHelper.getTypeName(ckass) + " )$0;\r\n");
		if (strategy != null && strategy.isUseTracker())
		{
			cache.append("int _$reIndex = _$tracker.indexOf($0);\r\n");
		}
		cache.append("cache.append('{');\r\n");
		Method[] methods = ReflectUtil.listGetMethod(ckass);
		Arrays.sort(methods, new MethodComparator());
		for (Method each : methods)
		{
			if (needIgnore(each, strategy))
			{
				continue;
			}
			WriteMethodInfo methodInfo = MethodInfoBuilder.buildWriteMethodInfo(each, strategy, entityName);
			cache.append(methodInfo.getOutput());
		}
		cache.append("if(cache.isCommaLast())\r\n{\r\n\tcache.deleteLast();\r\n}\r\n");
		cache.append("cache.append('}');\r\n}");
		return cache.toString();
	}
	
	private static Class<?> implWriter(String methodBody, Class<?> ckass, WriteStrategy strategy)
	{
		try
		{
			CompilerModel compilerModel = new CompilerModel("JsonWriter_" + count.getAndIncrement(), WriterAdapter.class);
			compilerModel.addImport(StringCache.class, WriterContext.class, Tracker.class, Iterator.class, Map.class, Set.class, Entry.class, JsonWriter.class);
			if (strategy != null)
			{
				createStrategyConstructor(compilerModel);
			}
			Method method = WriterAdapter.class.getMethod("write", Object.class, StringCache.class, Object.class, Tracker.class);
			MethodModel methodModel = new MethodModel(method);
			methodModel.setBody(methodBody);
			compilerModel.putMethod(method, methodModel);
			logger.trace("{}创建的源码是\r{}\r", ckass.getName(), compilerModel.toStringWithLineNo());
			JavaStringCompiler compiler = new JavaStringCompiler();
			return compiler.compile(compilerModel, ckass.getClassLoader());
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}
	
	private static void createStrategyConstructor(CompilerModel model)
	{
		model.addField(new FieldModel("writeStrategy", WriteStrategy.class));
		model.addConstructor("this.writeStrategy = $0;", WriteStrategy.class);
	}
	
	private static boolean needIgnore(Method method, WriteStrategy strategy)
	{
		String fieldName = ReflectUtil.getFieldNameFromMethod(method);
		if (method.isAnnotationPresent(JsonIgnore.class) && method.getAnnotation(JsonIgnore.class).force())
		{
			return true;
		}
		try
		{
			Field field = method.getDeclaringClass().getDeclaredField(fieldName);
			if (field.isAnnotationPresent(JsonIgnore.class) && field.getAnnotation(JsonIgnore.class).force())
			{
				return true;
			}
		}
		catch (Exception e)
		{
		}
		if (strategy != null)
		{
			if (strategy.ignore(method.getDeclaringClass().getName() + '.' + fieldName))
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		if (method.isAnnotationPresent(JsonIgnore.class))
		{
			return true;
		}
		try
		{
			Field field = method.getDeclaringClass().getDeclaredField(fieldName);
			if (field.isAnnotationPresent(JsonIgnore.class))
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		catch (Exception e)
		{
			return false;
		}
	}
	
	private static String implArrayWriterMethod(Class<?> targetClass, WriteStrategy strategy)
	{
		Class<?> rootType = targetClass;
		int dim = 0;
		while (rootType.isArray())
		{
			dim++;
			rootType = rootType.getComponentType();
		}
		String rootName = rootType.getName();
		String str;
		if (strategy == null)
		{
			str = "{\r\n\t" + NameTool.buildDimTypeName(rootName, dim) + " array" + dim + " = (" + NameTool.buildDimTypeName(rootName, dim) + ")$0;\r\n";
			str += "\tStringCache cache = (StringCache)$1;\r\n";
			str += "\tcache.append('[');\r\n";
			str += "\tint l" + dim + " = array" + dim + ".length;\r\n";
			String bk = "\t";
			for (int i = dim; i > 1; i--)
			{
				int next = i - 1;
				str += bk + "for(int i" + i + " = 0;i" + i + "<l" + i + ";i" + i + "++)\r\n";
				str += bk + "{\r\n";
				str += bk + "\tif(array" + i + "[i" + i + "] == null){continue;}\r\n";
				str += bk + "\tcache.append('[');\r\n";
				str += bk + "\t" + NameTool.buildDimTypeName(rootName, next) + " array" + next + " = array" + i + "[i" + i + "];\r\n";
				str += bk + "\tint l" + next + " =  array" + next + ".length;\r\n";
				bk += "\t";
			}
			str += bk + "for(int i1=0;i1<l1;i1++)\r\n";
			str += bk + "{\r\n";
			str += bk + "\tif(array1[i1]==null){continue;}\r\n";
			str += bk + "\tWriterContext.write(array1[i1],cache);\r\n";
			str += bk + "\tcache.append(',');\r\n";
			str += bk + "}\r\n";
			for (int i = dim; i > 1; i--)
			{
				str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
				str += bk + "cache.append(']');\r\n";
				str += bk + "cache.append(',');\r\n";
				bk = bk.substring(0, bk.length() - 1);
				str += bk + "}\r\n";
			}
			str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
			str += bk + "cache.append(']');\r\n";
			str += "}";
		}
		else
		{
			if (strategy.isUseTracker())
			{
				str = "{\r\n\t" + NameTool.buildDimTypeName(rootName, dim) + " array" + dim + " = (" + NameTool.buildDimTypeName(rootName, dim) + ")$0;\r\n";
				str += "\tStringCache cache = (StringCache)$1;\r\n";
				str += "\tTracker _$tracker = (Tracker)$3;\r\n";
				str += "\tint _$reIndex = _$tracker.indexOf($0);\r\n";
				str += "\tcache.append('[');\r\n";
				str += "\tint l" + dim + " = array" + dim + ".length;\r\n";
				String bk = "\t";
				for (int i = dim; i > 1; i--)
				{
					int next = i - 1;
					int pre = i + 1;
					str += bk + "for(int i" + i + " = 0;i" + i + "<l" + i + ";i" + i + "++)\r\n";
					str += bk + "{\r\n";
					String nowArrayName = "array" + i + "[i" + i + "]";
					String nowIndex = "_$index" + nowArrayName;
					String preArrayName = "array" + pre + "[i" + pre + "]";
					String preIndex = "_$index" + preArrayName;
					str += bk + "\tif(" + nowArrayName + " == null){continue;}\r\n";
					if (i == dim)
					{
						str += bk + "\t_$tracker.reset(_$reIndex);\r\n";
					}
					else
					{
						str += bk + "\t_$tracker.reset(" + preIndex + ");\r\n";
					}
					str += bk + "\tint " + nowIndex + " = _$tracker.indexOf(" + nowArrayName + ");\r\n";
					str += bk + "\tif(" + nowArrayName + "!= -1)\r\n";
					str += bk + "\t{\r\n";
					String writerName = "writer_array_" + i;
					str += bk + "\t\tJsonWriter " + writerName + " = writeStrategy.getTrackerType(" + nowArrayName + ".getClass());\r\n";
					str += bk + "\t\tif(" + writerName + " != null)\r\n";
					str += bk + "\t\t{\r\n";
					str += bk + "\t\t\t" + writerName + ".write(" + nowArrayName + ",cache,$1,_$tracker);\r\n";
					str += bk + "\t\t}\r\n";
					str += bk + "\t\telse\r\n";
					str += bk + "\t\t{\r\n";
					str += bk + "\t\t\tcache.append(\"{\\\"$ref\\\":\\\"\").append(_$tracker.getPath(" + nowIndex + ")).append('\"').append('}');\r\n";
					str += bk + "\t\t}\r\n";
					str += bk + "\t\tcontinue;\r\n";
					str += bk + "\t}\r\n";
					str += bk + "\tcache.append('[');\r\n";
					str += bk + "_$tracker.put(" + nowArrayName + ",\"[\"+i+" + i + "']',true);\r\n";
					str += bk + "\t" + NameTool.buildDimTypeName(rootName, next) + " array" + next + " = array" + i + "[i" + i + "];\r\n";
					str += bk + "\tint l" + next + " =  array" + next + ".length;\r\n";
					bk += "\t";
				}
				str += bk + "for(int i1=0;i1<l1;i1++)\r\n";
				str += bk + "{\r\n";
				str += bk + "\tif(array1[i1]==null){continue;}\r\n";
				if (dim == 1)
				{
					str += bk + "\t_$tracker.reset(_$reIndex);\r\n";
				}
				else
				{
					str += bk + "\t_$tracker.reset(_$indexarray2[i2]);\r\n";
				}
				str += bk + "\tint _$indexarray0 = _$tracker.indexOf(array1[i1]);\r\n";
				str += bk + "\tif(_$indexarray0 != -1)\r\n";
				str += bk + "\t{\r\n";
				str += bk + "\t\tJsonWriter writer_array_0 = writeStrategy.getTrackerType(array1[i1].getClass());\r\n";
				str += bk + "\t\tif(writer_array_0 != null)\r\n";
				str += bk + "\t\t{\r\n";
				str += bk + "\t\t\twriter_array_0.write(array1[i1],cache,$0,_$tracker);\r\n";
				str += bk + "\t\t}\r\n";
				str += bk + "\t\telse\r\n";
				str += bk + "\t\t{\r\n";
				str += bk + "\t\t\tcache.append(\"{\\\"$ref\\\":\\\"\").append(_$tracker.getPath(_$indexarray0)).append('\"').append('}');\r\n";
				str += bk + "\t\t}\r\n";
				str += bk + "\t\tcontinue;\r\n";
				str += bk + "\t}\r\n";
				str += bk + "\t_$tracker.put(array1[i1],\"[\"+i1+']',true);\r\n";
				str += bk + "\twriteStrategy.getWriter(array1[i1].getClass()).write(array1[i1],cache,$0,_$tracker);\r\n";
				str += bk + "\tcache.append(',');\r\n";
				str += bk + "}\r\n";
				for (int i = dim; i > 1; i--)
				{
					str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
					str += bk + "cache.append(']');\r\n";
					str += bk + "cache.append(',');\r\n";
					bk = bk.substring(0, bk.length() - 1);
					str += bk + "}\r\n";
				}
				str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
				str += bk + "cache.append(']');\r\n";
				str += "}";
			}
			else
			{
				str = "{\r\n\t" + NameTool.buildDimTypeName(rootName, dim) + " array" + dim + " = (" + NameTool.buildDimTypeName(rootName, dim) + ")$0;\r\n";
				str += "\tStringCache cache = (StringCache)$1;\r\n";
				str += "\tcache.append('[');\r\n";
				str += "\tint l" + dim + " = array" + dim + ".length;\r\n";
				String bk = "\t";
				for (int i = dim; i > 1; i--)
				{
					int next = i - 1;
					str += bk + "for(int i" + i + " = 0;i" + i + "<l" + i + ";i" + i + "++)\r\n";
					str += bk + "{\r\n";
					str += bk + "\tif(array" + i + "[i" + i + "] == null){continue;}\r\n";
					str += bk + "\tcache.append('[');\r\n";
					str += bk + "\t" + NameTool.buildDimTypeName(rootName, next) + " array" + next + " = array" + i + "[i" + i + "];\r\n";
					str += bk + "\tint l" + next + " =  array" + next + ".length;\r\n";
					bk += "\t";
				}
				str += bk + "for(int i1=0;i1<l1;i1++)\r\n";
				str += bk + "{\r\n";
				str += bk + "\tif(array1[i1]==null){continue;}\r\n";
				str += bk + "\twriteStrategy.getWriter(array1[i1].getClass()).write(array1[i1],cache,$0,null);\r\n";
				str += bk + "\tcache.append(',');\r\n";
				str += bk + "}\r\n";
				for (int i = dim; i > 1; i--)
				{
					str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
					str += bk + "cache.append(']');\r\n";
					str += bk + "cache.append(',');\r\n";
					bk = bk.substring(0, bk.length() - 1);
					str += bk + "}\r\n";
				}
				str += bk + "if(cache.isCommaLast()){cache.deleteLast();}\r\n";
				str += bk + "cache.append(']');\r\n";
				str += "}";
			}
		}
		return str;
	}
	
	public static void putwriter(Class<?> cklass, JsonWriter jsonWriter)
	{
		writerMap.put(cklass, jsonWriter);
	}
}
